iT邦幫忙

2024 iThome 鐵人賽

DAY 27
1
Modern Web

前進React 生態系 : 技術應用與概念解析系列 第 27

Day 27 - Streaming SSR 原理解析

  • 分享至 

  • xImage
  •  

流程說明

  1. Server 端開始渲染:React 在 Server 端逐步渲染組件並生成 HTML。
  2. Streaming HTML:當部分 HTML 完成時,立刻傳送到客戶端。
  3. 瀏覽器接收並渲染 HTML:瀏覽器開始接收 HTML,並在後台載入 JavaScript 資源。
  4. Hydration:React 將靜態的 HTML 轉換為可以互動的 React 元件,這個過程稱為 Hydration。

Server 怎麼回傳 HTML ?

以下範例將說明如何使用 renderToPipeableStream 在 Node.js 環境中使用 Streaming SSR:

import { renderToPipeableStream } from "react-dom/server";

app.use("/", (request, response) => {
  const { pipe } = renderToPipeableStream(<App />, {
    bootstrapScripts: ["/main.js"],
    onShellReady() {
      response.setHeader("content-type", "text/html");
      pipe(response);
    },
  });
});
  • bootstrapScripts : 要在 Client 端載入的 JavaScript 檔案。
  • onShellReady :在初始架構 (shell) 渲染後立即觸發。
  • pipe : 將生成的 HTML 傳到 Client 端。

onShellReadyonAllReady

除了 onShellReady,還有 onAllReady的選項,以下是兩者的差別:

  • onShellReady:在初始 shell (頁面架構) 渲染後立即觸發。
  • onAllReady:在所有都渲染完成時觸發。

範例說明:

function ProfilePage() {
  return (
    <ProfileLayout>
      <ProfileCover />
      <Suspense fallback={<BigSpinner />}>
        <Sidebar>
          <Friends />
          <Photos />
        </Sidebar>
        <Suspense fallback={<PostsGlimmer />}>
          <Posts />
        </Suspense>
      </Suspense>
    </ProfileLayout>
  );
}

透過 Suspense,React 可以在資料尚未完全載入前,先傳輸頁面的主要架構(shell)。之後再逐步載入資料並替換 fallback UI。

其他方法

除了 renderToPipeableStream 之外,還有其他方法回傳 HTML:

  • renderToStaticNodeStream : 適用於 Node.js 環境,生成的是非互動的靜態頁面。
  • renderToReadableStream : 適用於 Web Streams 環境(例如 Deno 或 edge runtimes),使用 Web Streams API 傳輸 HTML。
  • renderToString: 直接將 React 組件渲染為靜態 HTML 字串,但不支援 Streaming。
  • renderToStaticMarkup: 與 renderToString 類似,但生成的是非互動的靜態頁面。

React 怎麼 Hydration 的 ?

在 Server 端完成 SSR,將靜態 HTML 傳送至客戶端後,React 會透過 hydrateRoot 將這些靜態的 HTML 轉換為可互動的 React 元件。

基本架構:

const root = hydrateRoot(domNode, reactNode, options?)
  • domNode : 在 html 中對應的根元素的 DOM 節點
  • reactNode: 用來生成對應 HTML 的 React 元件,像是 <App />

使用範例:

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";

hydrateRoot(document.getElementById("root"), <App />);

需要注意的是,hydrateRoot 的預期內容必須與 Server 端渲染的 HTML 完全一致,否則可能會導致 Hydration 錯誤。

Hydration 的技術細節

在 React Fiber 中有一個屬性 stateNode,用來指向 Fiber 所對應的真實 DOM 節點。hydrateRoot 的實際流程會是在 render 階段創建 DOM 節點,並在 commit 階段更新 DOM 節點。

Hydration 錯誤

由於 Server 端生成的 HTML 和客戶端渲染的 React 樹必須完全一致,有些微的不同都有可能導致 Hydration 錯誤。這方面對使用者體驗也很重要,透過先將 HTML 展示給使用者會營造出應用快速載入的效果。如果內容不一致就會讓使用者覺得很奇怪。

以下是常見的 Hydration 錯誤:

  • 在根節點內 React 生成的 HTML 周圍有額外的空格或換行
  • 在選染邏輯使用typeof window !== 'undefined'
  • 使用瀏覽器專屬 API
  • Server 和 Client 端的資料不一致

React 可以修復部分 Hydration 問題,但修復的過程可能會導致性能降低,甚至有可能將事件處理函數會綁定到錯誤的元素。

參考資料:
https://react.dev/reference/react-dom/server
https://react.dev/reference/react-dom/server/renderToPipeableStream
https://react.dev/reference/react-dom/client/hydrateRoot
https://github.com/Reactwg/React-18/discussions/37
https://juejin.cn/post/7165699863416406029
https://blog.logrocket.com/react-hydration-pre-rendered-html/


上一篇
Day 26 - React Fiber 和 Concurrent 原理解析
下一篇
Day 28 - React Server Components 原理解析
系列文
前進React 生態系 : 技術應用與概念解析30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言